/**
 * @author: 團長
 * @date: 2014-2-12-下午5:51:10
 */
package net.loyintean.pearlshell.task.bus;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

import org.apache.log4j.Logger;

import net.loyintean.pearlshell.base.model.ILogInfo;
import net.loyintean.pearlshell.base.model.IResult;
import net.loyintean.pearlshell.base.model.impl.LogInfoFactory;

/**
 * Basic implements to execute tasks. This class contains a ExecutorService
 * which accept tasks this class submits to, checks the results and encapsulates
 * them into IResult. If one of the tasks failed or throws exception, this class
 * will try to cancel the others.
 * <p>
 * The {@link #executorService} is instanced as Executors.newCachedThreadPool()
 * by default. You might assign another instance through the constructor or
 * setter.
 * 
 * 
 * @author 團長
 * @since 2014-2-17
 * @version 1.0.0
 */
public class BasicTaskExecuteServImpl<T> implements ITaskExecuteService<T> {

	/**
	 * logger
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 */
	protected final Logger LOGGER = Logger.getLogger(this.getClass());

	// done wuyichen 2014-2-14 这里是不是弄成方法更好？这样子类可以重写，或者根据需求不一样，需要建立不同的线程池。
	// 增加了构造方法和set方法；这样如果需要覆盖默认实现，只要利用set方法重新写入一个即可。
	private ExecutorService executorService;

	/**
	 * constructor. instance the {@link #executorService} as
	 * Executors.newCachedThreadPool() by default.
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 */
	public BasicTaskExecuteServImpl() {
		this.executorService = Executors.newCachedThreadPool();
	}

	/**
	 * constructor specified the {@link #executorService}.
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param _executorService
	 */
	public BasicTaskExecuteServImpl(ExecutorService _executorService) {
		super();
		this.executorService = _executorService;
	}

	/**
	 * execute the tasks and record the result into the parameter result.
	 * 
	 * @author 團長
	 * @since 2014-2-12
	 * @see com.sinosig.evaluation.base.service.task.ITaskExecuteService#executeTasks(java.util.List)
	 * 
	 */
	@Override
	public IResult<T> executeTasks(List<FutureTask<T>> tasks, IResult<T> result) {

		ILogInfo logInfo = LogInfoFactory.BUILD("@task=" + tasks + "@result="
				+ result);

		LOGGER.info(logInfo.start());

		this.submitTasks(tasks);

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(logInfo.inner("task list has been submitted"));
		}

		result = this.checkTaskResult(tasks, result);

		LOGGER.info(logInfo.end("result=" + result));

		return result;
	}

	/**
	 * submit tasks to {@link #executorService}. executorService will try to
	 * execute tasks as soon as possible after submit them.
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param taskList
	 */
	private void submitTasks(List<FutureTask<T>> taskList) {
		assert taskList != null;

		// 逐个提交任务
		for (FutureTask<T> task : taskList) {
			executorService.submit(task);
		}

	}

	/**
	 * check results of tasks. This method will block this thread when the
	 * task.get(). If any one of tasks failed(throws exception), the result will
	 * be exception. And other tasks will be cancel if possible.
	 * <p>
	 * You might process each task and it's result by override method
	 * {@link #recordTaskResult(FutureTask, IResult)}
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param taskList
	 *            list of tasks
	 * @param result
	 *            result of tasks before this method.
	 * @return result of tasks. If all tasks successed, it will be success and
	 *         contain the last task's result as it's data. Else, it will be
	 *         exception.
	 */
	private IResult<T> checkTaskResult(List<FutureTask<T>> taskList,
			IResult<T> result) {

		assert taskList != null;
		assert result != null;

		// 逐个获取任务结果
		FutureTask<T> task = null;
		// 任务序列号
		int taskIndex = 0;
		try {
			// taskIndex=0 has assigned before.
			for (; taskIndex < taskList.size(); taskIndex++) {

				task = taskList.get(taskIndex);

				this.recordTaskResult(task, result);
			}
		} catch (InterruptedException e) {
			this.handleException(result, task, e);

		} catch (ExecutionException e) {
			// 标记任务列表中某个任务发生了异常
			this.handleException(result, task, e);
		} finally {
			// 如果上述操作发生异常，则经过handleException的处理后，操作结果的状态会变成exception
			if (result.isException()) {
				this.cancelRemainningTask(taskList.subList(taskIndex,
						taskList.size()));
			}
		}

		return result;
	}

	/**
	 * record the result of taks into input parameter _result. By default, this
	 * method tag _result as success and set it's data by task.get().
	 * <p>
	 * This method will block the thread when task.get().
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param task
	 *            the task which has finished it's logic
	 * @param _result
	 *            the result of task.
	 * @throws InterruptedException
	 *             if the task was interrupted
	 * @throws ExecutionException
	 *             if any exceptin throws in the task
	 */
	protected void recordTaskResult(FutureTask<T> task, IResult<T> _result)
			throws InterruptedException, ExecutionException {
		assert task != null;

		_result.success(task.get());
	}

	/**
	 * handle the exception of task. By default, this method will record the
	 * task and exception into the _result.
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param _result
	 *            the result of task.
	 * @param task
	 *            the task which throws exception.
	 * @param e
	 *            exception throwed by the task.
	 * @return the exceptinoned result.
	 */
	private IResult<T> handleException(IResult<T> _result, FutureTask<T> task,
			Exception e) {
		// 标记任务列表中某个任务发生了异常
		_result.exception(null, "the task " + task + " throws an exception as "
				+ e.getMessage(), e);
		return _result;
	}

	/**
	 * try to cancel tasks in taskLit.
	 * <p>
	 * this method can't guarantee that the tasks will always be canceled
	 * successful. it just try it's best to do this.
	 * 
	 * @author 團長
	 * @since 2014-2-17
	 * @param taskList
	 *            the tasks to be cancel.
	 */
	private void cancelRemainningTask(List<FutureTask<T>> taskList) {
		for (FutureTask<T> futureTask : taskList) {
			futureTask.cancel(true);
		}
	}

	/**
	 * @param executorService
	 *            the {@link #executorService }to set
	 */
	public void setExecutorService(ExecutorService executorService) {
		this.executorService = executorService;
	}

}
